// CodeContracts
// 
// Copyright (c) Microsoft Corporation
// 
// All rights reserved. 
// 
// MIT License
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
#if CCINamespace
using Microsoft.Cci;
#else
using System.Compiler;
#endif
using System.Diagnostics;
using System.IO;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using System.Diagnostics.Contracts;

#if CCINamespace
namespace Microsoft.Cci.Metadata{
#else
namespace System.Compiler.Metadata{
#endif
  unsafe internal sealed class MemoryCursor{
    private byte* buffer, pb;
    readonly internal int Length;

#if !ROTOR
    internal MemoryCursor(MemoryMappedFile/*!*/ memoryMap)
      :this(memoryMap.Buffer, memoryMap.Length){
    }
#endif    
    internal MemoryCursor(byte* buffer, int length, int position){
      this.buffer = buffer;
      this.pb = buffer+position;
      this.Length = length;
    }
    internal MemoryCursor(byte *buffer, int length) 
      : this(buffer, length, 0){ 
    }
    internal MemoryCursor(MemoryCursor/*!*/ c) {
      this.buffer = c.buffer;
      this.pb = c.pb;
      this.Length = c.Length;
    }

    internal byte* GetBuffer(){
      return this.buffer;
    }

    internal int Position{ 
      get{ return (int)(this.pb-this.buffer); } 
      set{ this.pb = this.buffer+value; }
    }
    internal void Align(int size){
      Contract.Requires(size == 2 || size == 4 || size == 8 || size == 16 || size == 32 || size == 64);
      int remainder = Position & (size-1);
      if (remainder != 0)
        pb += size-remainder;
    }

    //internal System.Char Char(int i){ return *(System.Char*)(this.pb+i*sizeof(System.Char)); }
    //internal System.SByte SByte(int i){ return *(System.SByte*)(this.pb+i*sizeof(System.SByte)); }
    internal System.Int16 Int16(int i){ return *(System.Int16*)(this.pb+i*sizeof(System.Int16)); }
    internal System.Int32 Int32(int i){ return *(System.Int32*)(this.pb+i*sizeof(System.Int32)); }
    //internal System.Int64 Int64(int i){ return *(System.Int64*)(this.pb+i*sizeof(System.Int64)); }
    internal System.Byte Byte(int i){ return *(System.Byte*)(this.pb+i*sizeof(System.Byte)); }
    internal System.UInt16 UInt16(int i){ return *(System.UInt16*)(this.pb+i*sizeof(System.UInt16)); }
    //internal System.UInt32 UInt32(int i){ return *(System.UInt32*)(this.pb+i*sizeof(System.UInt32)); }
    //internal System.UInt64 UInt64(int i){ return *(System.UInt64*)(this.pb+i*sizeof(System.UInt64)); }
    //internal System.Boolean Boolean(int i){ return *(System.Boolean*)(this.pb+i*sizeof(System.Boolean)); }
    //internal System.Single Single(int i){ return *(System.Single*)(this.pb+i*sizeof(System.Single)); }
    //internal System.Double Double(int i){ return *(System.Double*)(this.pb+i*sizeof(System.Double)); }

    //internal void SkipChar(int c){ this.pb += c*sizeof(System.Char); }
    //internal void SkipSByte(int c){ this.pb += c*sizeof(System.SByte); }
    internal void SkipInt16(int c){ this.pb += c*sizeof(System.Int16); }
    internal void SkipInt32(int c){ this.pb += c*sizeof(System.Int32); }
    //internal void SkipInt64(int c){ this.pb += c*sizeof(System.Int64); }
    internal void SkipByte(int c){ this.pb += c*sizeof(System.Byte); }
    internal void SkipUInt16(int c){ this.pb += c*sizeof(System.UInt16); }
    //internal void SkipUInt32(int c){ this.pb += c*sizeof(System.UInt32); }
    //internal void SkipUInt64(int c){ this.pb += c*sizeof(System.UInt64); }
    //internal void SkipBoolean(int c){ this.pb += c*sizeof(System.Boolean); }
    //internal void SkipSingle(int c){ this.pb += c*sizeof(System.Single); }
    //internal void SkipDouble(int c){ this.pb += c*sizeof(System.Double); }

    internal System.Char ReadChar(){ byte *pb = this.pb; System.Char v = *(System.Char*)pb; this.pb = pb+sizeof(System.Char); return v; }
    internal System.SByte ReadSByte(){ byte *pb = this.pb; System.SByte v = *(System.SByte*)pb; this.pb = pb+sizeof(System.SByte); return v; }
    internal System.Int16 ReadInt16(){ byte *pb = this.pb; System.Int16 v = *(System.Int16*)pb; this.pb = pb+sizeof(System.Int16); return v; }
    internal System.Int32 ReadInt32(){ byte *pb = this.pb; System.Int32 v = *(System.Int32*)pb; this.pb = pb+sizeof(System.Int32); return v; }
    internal System.Int64 ReadInt64(){ byte *pb = this.pb; System.Int64 v = *(System.Int64*)pb; this.pb = pb+sizeof(System.Int64); return v; }
    internal System.Byte ReadByte(){ byte *pb = this.pb; System.Byte v = *(System.Byte*)pb; this.pb = pb+sizeof(System.Byte); return v; }
    internal System.UInt16 ReadUInt16(){ byte *pb = this.pb; System.UInt16 v = *(System.UInt16*)pb; this.pb = pb+sizeof(System.UInt16); return v; }
    internal System.UInt32 ReadUInt32(){ byte *pb = this.pb; System.UInt32 v = *(System.UInt32*)pb; this.pb = pb+sizeof(System.UInt32); return v; }
    internal System.UInt64 ReadUInt64(){ byte *pb = this.pb; System.UInt64 v = *(System.UInt64*)pb; this.pb = pb+sizeof(System.UInt64); return v; }
    internal System.Boolean ReadBoolean(){ byte *pb = this.pb; System.Boolean v = *(System.Boolean*)pb; this.pb = pb+sizeof(System.Boolean); return v; }
    internal System.Single ReadSingle(){ byte *pb = this.pb; System.Single v = *(System.Single*)pb; this.pb = pb+sizeof(System.Single); return v; }
    internal System.Double ReadDouble(){ byte *pb = this.pb; System.Double v = *(System.Double*)pb; this.pb = pb+sizeof(System.Double); return v; }

    internal System.Int32 ReadReference(int refSize){
      if (refSize == 2) return ReadUInt16();
      return ReadInt32();
    }

    internal int ReadCompressedInt(){
      byte headerByte = ReadByte();
      int result;
      if ((headerByte & 0x80) == 0x00)
        result = headerByte;
      else if ((headerByte & 0x40) == 0x00)
        result = ((headerByte & 0x3f) << 8) | ReadByte();
      else if (headerByte == 0xFF)
        result = -1;
      else
        result = ((headerByte & 0x3f) << 24) | (ReadByte() << 16) | (ReadByte() << 8) | ReadByte();
      return result;
    }

    internal byte[]/*!*/ ReadBytes(int c){
      byte[] result = new byte[c];
      byte *pb = this.pb;
      for (int i = 0; i < c; i++)
        result[i] = *pb++;
      this.pb = pb;
      return result;
    }

    internal unsafe Identifier/*!*/ ReadIdentifierFromSerString() {
      byte *pb = this.pb;
      byte headerByte = *pb++;
      uint length = 0;
      if ((headerByte & 0x80) == 0x00)
        length = headerByte;
      else if ((headerByte & 0x40) == 0x00)
        length = (uint)((headerByte & 0x3f) << 8) | *pb++;
      else
        length = (uint)((headerByte & 0x3f) << 24) | (uint)(*pb++ << 16) | (uint)(*pb++ << 8) | (*pb++);
      this.pb = pb+length;
      return Identifier.For(pb, length/*, this.KeepAlive*/);
    }

    internal string/*!*/ ReadUTF8(int bytesToRead) {
      char[] buffer = new char[bytesToRead];
      byte *pb = this.pb;
      this.pb += bytesToRead;
      int j = 0;
      while (bytesToRead > 0){
        byte b = *pb++; bytesToRead--;
        if ((b & 0x80) == 0 || bytesToRead == 0){
          buffer[j++] = (char)b;
          continue;
        }
        char ch;
        byte b1 = *pb++; bytesToRead--;
        if ((b & 0x20) == 0)
          ch = (char)(((b&0x1F)<<6) | (b1&0x3F));
        else{
          if (bytesToRead == 0){ //Dangling lead bytes, do not decompose
            buffer[j++] = (char)((b << 8) | b1);
            break;
          }
          byte b2 = *pb++; bytesToRead--;
          uint ch32;
          if ((b & 0x10) == 0)
            ch32 = (uint)(((b&0x0F)<<12) | ((b1&0x3F)<<6) | (b2&0x3F));
          else{
            if (bytesToRead == 0){ //Dangling lead bytes, do not decompose
              buffer[j++] = (char)((b << 8) | b1);
              buffer[j++] = (char)b2;
              break;
            }
            byte b3 = *pb++; bytesToRead--;
            ch32 = (uint)(((b&0x07)<<18) | ((b1&0x3F)<<12) | ((b2&0x3F)<<6) | (b3&0x3F));
          }
          if ((ch32 & 0xFFFF0000) == 0)
            ch = (char)ch32;
          else{ //break up into UTF16 surrogate pair
            buffer[j++] = (char)((ch32 >> 10) | 0xD800);
            ch = (char)((ch32 & 0x3FF) | 0xDC00);
          }
        }
        buffer[j++] = ch;
      }
      if (j > 0 && buffer[j-1] == 0) j--;
      return new String(buffer, 0, j);
    }

    internal string/*!*/ ReadUTF8(){ 
      byte *pb = this.pb;
      StringBuilder sb = new StringBuilder();
      byte b = 0;
      for(;;){
        b = *pb++;
        if (b == 0) break;
        if ((b & 0x80) == 0){
          sb.Append((char)b);
          continue;
        }
        char ch;
        byte b1 = *pb++;
        if (b1 == 0){ //Dangling lead byte, do not decompose
          sb.Append((char)b);
          break;
        }
        if ((b & 0x20) == 0){
          ch = (char)(((b&0x1F)<<6) | (b1&0x3F));
        }else{
          byte b2 = *pb++;
          if (b2 == 0){ //Dangling lead bytes, do not decompose
            sb.Append((char)((b << 8)|b1));
            break;
          }
          uint ch32;
          if ((b & 0x10) == 0)
            ch32 = (uint)(((b&0x0F)<<12) | ((b1&0x3F)<<6) | (b2&0x3F));
          else{
            byte b3 = *pb++;
            if (b3 == 0){ //Dangling lead bytes, do not decompose
              sb.Append((char)((b << 8)|b1));
              sb.Append((char)b2);
              break;
            }
            ch32 = (uint)(((b&0x07)<<18) | ((b1&0x3F)<<12) | ((b2&0x3F)<<6) | (b3&0x3F));
          }
          if ((ch32 & 0xFFFF0000) == 0)
            ch = (char)ch32;
          else{ //break up into UTF16 surrogate pair
            sb.Append((char)((ch32 >> 10) | 0xD800));
            ch = (char)((ch32 & 0x3FF) | 0xDC00);
          }
        }
        sb.Append(ch);
      }
      this.pb = pb;
      return sb.ToString();
    }

    internal string/*!*/ ReadUTF16(int charsToRead){
      char *pc = (char*)this.pb;
      char[] buffer = new char[charsToRead];
      for (int i = 0; i < charsToRead; i++)
        buffer[i] = *pc++;
      this.pb = (byte*)pc;
      return new String(buffer, 0, charsToRead);
    }

    internal string/*!*/ ReadUTF16() {
      string result = new string((char*)this.pb);
      this.pb += (result.Length+1)*2;
      return result;
    }

    internal string/*!*/ ReadASCII(int bytesToRead) {
      int c = bytesToRead;
      if (bytesToRead == -1) c = 128; //buffer size
      byte *pb = this.pb;
      char[] buffer = new char[c];
      int j = 0;
      byte b = 0;
    Restart:
      while (j < c){
        b = *pb++;
        if (b == 0) break;
        buffer[j++] = (char)b;
      }
      if (bytesToRead == -1){
        if (b != 0){
          char[] newBuffer = new char[c *= 2];
          for (int copy = 0; copy < j; copy++)
            newBuffer[copy] = buffer[copy];
          buffer = newBuffer;
          goto Restart;
        }
        this.pb = pb;
      }else
        this.pb += bytesToRead;
      return new String(buffer, 0, j);
    }

    internal string/*!*/ ReadASCII() { return ReadASCII(-1); }
  }

#if !ROTOR
#if !FxCop
  /// <summary>
  /// Public only for use by the Framework. Do not use this class.
  /// Well, if you really really must, use it only if you can tolerate keeping the file locked for at least as long as any Identifier
  /// derived from the file stays alive.
  /// </summary>   
  unsafe public sealed class MemoryMappedFile : IDisposable, ISourceTextBuffer{
#else
  unsafe sealed class MemoryMappedFile : IDisposable{
#endif
    private byte* buffer;
    private int length;

    public MemoryMappedFile(string fileName){
      this.OpenMap(fileName);
    }
    ~MemoryMappedFile(){ 
      this.CloseMap(); 
    }
    public void Dispose(){ 
      this.CloseMap(); 
      GC.SuppressFinalize(this);
    }

    public byte *Buffer{
      get {
        Debug.Assert(this.buffer != null);
        return this.buffer;
      }
    }
    public int Length {
      get { 
        Debug.Assert(this.buffer != null);
        return this.length;
      }
    }
#if !FxCop
    string ISourceText.Substring(int start, int length){
      Debug.Assert(false, "Can't use Substring on memory mapped files");
      return null;
    }
    char ISourceText.this[int index]{
      get{
        Debug.Assert(false, "Can't access memory mapped files via an indexer, use Buffer");
        return ' ';
      }
    }
#endif
    private void OpenMap(string filename){
      IntPtr hmap;
      int length;
      using (FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)){
        if (stream.Length > Int32.MaxValue)
          throw new FileLoadException(ExceptionStrings.FileTooBig, filename);
        length = unchecked((int)stream.Length);
#if WHIDBEY && !OldWhidbey
        hmap = CreateFileMapping(stream.SafeFileHandle.DangerousGetHandle(), IntPtr.Zero, PageAccess.PAGE_READONLY, 0, length, null);
#else
        hmap = CreateFileMapping(stream.Handle, IntPtr.Zero, PageAccess.PAGE_READONLY, 0, length, null);
#endif
        if (hmap == IntPtr.Zero){
          int rc = Marshal.GetLastWin32Error();
          throw new FileLoadException(String.Format(CultureInfo.CurrentCulture, 
            ExceptionStrings.CreateFileMappingReturnedErrorCode, rc.ToString()), filename);
        }
      }
      this.buffer = (byte *)MapViewOfFile(hmap, FileMapAccess.FILE_MAP_READ, 0, 0, (IntPtr)length);
      MemoryMappedFile.CloseHandle(hmap);
      if (this.buffer == null){
        int rc = Marshal.GetLastWin32Error();
        throw new FileLoadException(String.Format(CultureInfo.CurrentCulture, 
            ExceptionStrings.MapViewOfFileReturnedErrorCode, rc.ToString()), filename);
      }
      this.length = length;
    }      
    private void CloseMap(){
      if (buffer != null){
        UnmapViewOfFile(buffer);
        buffer = null;
      }
    }
#if !FxCop
    void ISourceText.MakeCollectible(){
    }
#endif

    private enum PageAccess : int { PAGE_READONLY = 0x02 };
    private enum FileMapAccess : int { FILE_MAP_READ = 0x0004 };

    [DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    private static extern IntPtr CreateFileMapping(
      IntPtr hFile,           // handle to file
      IntPtr lpAttributes,    // security
      PageAccess flProtect,   // protection
      int dwMaximumSizeHigh,  // high-order DWORD of size
      int dwMaximumSizeLow,   // low-order DWORD of size
      string lpName           // object name
      );

    [DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    private static extern void* MapViewOfFile(
      IntPtr hFileMappingObject,      // handle to file-mapping object
      FileMapAccess dwDesiredAccess,  // access mode
      int dwFileOffsetHigh,           // high-order DWORD of offset
      int dwFileOffsetLow,            // low-order DWORD of offset
      IntPtr dwNumberOfBytesToMap        // number of bytes to map
      );

    [DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UnmapViewOfFile(
      void* lpBaseAddress // starting address
      );

    [DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(
      IntPtr hObject  // handle to object
      );
  }
#endif
}
